package com.owc.service.impl.app;

import com.alibaba.fastjson.JSON;
import com.owc.common.Const.Const;
import com.owc.common.enums.*;
import com.owc.common.exception.BadRequestException;
import com.owc.common.redis.CacheBuildKey;
import com.owc.common.redis.RedisUtil;
import com.owc.common.tool.*;
import com.owc.dao.*;
import com.owc.domain.WalletAddress;
import com.owc.domain.entity.*;
import com.owc.domain.vo.admin.CallbackRequest;
import com.owc.domain.vo.admin.IntoToken;
import com.owc.domain.vo.app.ExchangeVo;
import com.owc.filter.content.SecurityContextHolder;
import com.owc.service.OwcService;
import com.owc.service.admin.AccountFlowService;
import com.owc.service.admin.FinanceService;
import com.owc.service.admin.SystemService;
import com.owc.service.app.WalletService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import static com.owc.common.tool.DateUtils.returnPreviousDay;

/**
 * @Author Jackies
 * @Date 2019/12/5 22:31
 * @Description TODO://
 **/
@Service
@Slf4j
public class TransactionService {

    @Resource
    private WithdrawFlowMapper withdrawFlowMapper;
    @Resource
    private TokenBalanceMapper tokenBalanceMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private TransferRecordMapper transferRecordMapper;
    @Resource
    private UserWalletAddressMapper userWalletAddressMapper;
    @Resource
    private RechargeFlowMapper rechargeFlowMapper;
    @Resource
    private TokenMapper tokenMapper;
    @Resource
    private DrawFlowMapper drawFlowMapper;
    @Resource
    private ExchangeMapper exchangeMapper;
    @Resource
    private IncomeRecordMapper incomeRecordMapper;
    @Resource
    private OrderMapper orderMapper;


    @Autowired
    private AccountFlowService accountFlowService;
    @Autowired
    private WalletService walletService;
    @Autowired
    private SystemService systemService;
    @Autowired
    private OwcService owcService;
    @Autowired
    private IDGenerater idGenerater;
    @Autowired
    private FinanceService financeService;

    @Transactional(rollbackFor = Exception.class)
    public void createWithdrawEntity(WithdrawFlow withdrawFlow) throws Exception {
        //Create a new withdraw order.
        withdrawFlowMapper.insert(withdrawFlow);
        //Deduct token balance
        tokenBalanceMapper.updateBalance(withdrawFlow.getUserId(), withdrawFlow.getTokenId(), withdrawFlow.getRealAmount());
        //Create flow info. By FlowTypeEnum.withdraw
        accountFlowService.insertFlow(FlowTypeEnum.withdraw, withdrawFlow.getUserId(), withdrawFlow.getRealAmount().multiply(new BigDecimal(-1)),
                withdrawFlow.getWalletAddress(), withdrawFlow.getTokenId(), withdrawFlow.getTransferId(), null, StatusEnum.PENDING);


        tokenBalanceMapper.updateBalance(withdrawFlow.getUserId(), withdrawFlow.getTokenId(), withdrawFlow.getHandlingFee());
        //Create flow info. By FlowTypeEnum.withdrawCost
        accountFlowService.insertFlow(FlowTypeEnum.withdrawCost, withdrawFlow.getUserId(), withdrawFlow.getHandlingFee().multiply(new BigDecimal(-1)),
                withdrawFlow.getWalletAddress(), withdrawFlow.getTokenId(), withdrawFlow.getTransferId(), null, StatusEnum.PENDING);
    }

    /**
     * 初始化用户信息
     * 1 保存用户基本账号密码信息
     * 2 根据token表定义的代币类型,初始化每个代币到token_balance表中,默认初始余额为0
     */
    @Transactional(rollbackFor = Exception.class)
    public void initUserInfo(User user) throws Exception {

        //Save user basis info.
        userMapper.insert(user);

        List<Token> allToken = owcService.getAllToken(); //Get all token type.

        if (allToken == null || allToken.isEmpty()) {
            throw new Exception("The token type is not defined, please contact the administrator to initialize the data");
        }

        WalletAddress walletAddress = JSON.parseObject(user.getWalletAddress(), WalletAddress.class);

        List<TokenBalance> initBalanceList = new ArrayList<>();
        //init user balance info.
        for (Token token : allToken) {
            TokenBalance tokenBalance = loadDefaultBalanceInfo(user.getId(), user.getUsername(), token.getTokenName(), token.getId());

            //Get china parent.
            String tokenName = (token.getParentId() == -1L ? token.getTokenName() : getParentName(allToken, token.getParentId()));
            if (UtilsService.isEth(tokenName)) {
                tokenBalance.setAddress(walletAddress.getEth());
            } else {
                tokenBalance.setAddress(walletAddress.getBtc());
            }
            initBalanceList.add(tokenBalance);
        }
        //Save token balance infos.
        tokenBalanceMapper.batchInsert(initBalanceList);

        //Save user wallet address mapper info.
        WalletAddress walletAddress1 = JSON.parseObject(user.getWalletAddress(), WalletAddress.class);
        UserWalletAddress userWalletAddress = new UserWalletAddress();
        userWalletAddress.setUserId(user.getId());
        userWalletAddress.setBtc(walletAddress1.getBtc());
        userWalletAddress.setEth(walletAddress1.getEth());
        userWalletAddressMapper.insert(userWalletAddress);
    }

    private String getParentName(List<Token> allToken, Long tokenId) {
        return allToken.parallelStream()
                .filter(t -> t.getId().equals(tokenId))
                .findFirst()
                .get()
                .getTokenName();
    }

    private TokenBalance loadDefaultBalanceInfo(Long userId, String userName, String tokenName, Long tokenId) throws Exception {
        String[] field = {"userId", "userName", "balance", "freeze", "tokenId", "tokenName"};
        Object[] values = {userId, userName, BigDecimal.ZERO, BigDecimal.ZERO, tokenId, tokenName};
        return BeanUtils.buildDefaultAttrModel(field, values, TokenBalance.class);
    }

    @Transactional(rollbackFor = Exception.class)
    public void refuse(List<WithdrawFlow> withdrawFlows) throws Exception {
        for (WithdrawFlow withdrawFlow : withdrawFlows) {
            //将转出流水状态更新为拒绝
            accountFlowService.updateFlowStatus(withdrawFlow.getTransferId(), StatusEnum.REFUSE);
            //批量更新余额,增加可用余额,减少冻结余额
            tokenBalanceMapper.updateBalance(withdrawFlow.getUserId(), withdrawFlow.getTokenId(), withdrawFlow.getRealAmount().multiply(new BigDecimal("-1")));
            //Create flow info. By FlowTypeEnum.withdrawRefuseBack
            accountFlowService.insertFlow(FlowTypeEnum.withdrawRefuseBack, withdrawFlow.getUserId(), withdrawFlow.getRealAmount(),
                    withdrawFlow.getWalletAddress(), withdrawFlow.getTokenId(), withdrawFlow.getTransferId(), null);

            tokenBalanceMapper.updateBalance(withdrawFlow.getUserId(), withdrawFlow.getTokenId(), withdrawFlow.getHandlingFee().multiply(new BigDecimal("-1")));
            //Create flow info. By FlowTypeEnum.withdrawRefuseBack
            accountFlowService.insertFlow(FlowTypeEnum.withdrawRefuseCostBack, withdrawFlow.getUserId(), withdrawFlow.getHandlingFee(),
                    withdrawFlow.getWalletAddress(), withdrawFlow.getTokenId(), withdrawFlow.getTransferId(), null);

            withdrawFlow.setStatus(StatusEnum.REFUSE.getType());
        }
        //批量更新用户订单状态为拒绝
        withdrawFlowMapper.updateBatch(withdrawFlows);
    }

    @Transactional(rollbackFor = Exception.class)
    public List<TransferRecord> createWithdrawFlow(List<WithdrawFlow> withdrawFlows, StatusEnum passed) {
        List<TransferRecord> recordList = new ArrayList<>();
        for (WithdrawFlow withdrawFlow : withdrawFlows) {
            withdrawFlow.setStatus(passed.getType());
            recordList.add(loadTransferRecord(withdrawFlow));
        }
        //更新订单状态
        withdrawFlowMapper.updateBatch(withdrawFlows);
        //创建区块链交易记录
        transferRecordMapper.batchInsert(recordList);
        return recordList;
    }

    private TransferRecord loadTransferRecord(WithdrawFlow withdrawFlow) {
        TransferRecord record = new TransferRecord();

        String transferId = UtilsService.getGuid();

        record.setCommittime(DateUtils.formatYMDHMS(new Date()));
        record.setStatus(withdrawFlow.getStatus());
        record.setTransferamount(withdrawFlow.getTotalAmount());
        record.setUserid(withdrawFlow.getUserId());
        record.setTokenid(withdrawFlow.getTokenId());
        record.setTokenname(withdrawFlow.getTokenName());
        record.setWithdrawflowid(withdrawFlow.getTransferId());
        record.setHandlingfree(withdrawFlow.getHandlingFee());
        record.setTxnttype(CallbackTypeEnum.WITHDRAW_TYPE.getType());
        record.setTransferid(transferId);
        return record;
    }


    private TransferRecord buildTransferRecord(DrawFlow drawFlow) {
        TransferRecord record = new TransferRecord();
        record.setStatus(StatusEnum.PASSED.getType());
        record.setCommittime(DateUtils.formatYMDHMS(new Date()));
        record.setUserid(drawFlow.getOperAccountId());
        record.setTokenid(drawFlow.getTokenId());
        record.setTokenname(drawFlow.getTokenName());
        record.setWithdrawflowid(drawFlow.getDrawoutId());
        record.setHandlingfree(BigDecimal.ZERO);
        record.setTxnttype(drawFlow.getType());
        record.setTransferid(UtilsService.getGuid());
        record.setTransferamount(drawFlow.getDrawoutAmount());
        record.setUserid(drawFlow.getOperAccountId());
        return record;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateWithdrawCallbackInfo(CallbackRequest callbackRequest) throws Exception {
        String traceId = callbackRequest.getTrace();
        //通过traceID获取到流水记录,然后拿到withdrawId
        TransferRecord transferRecord = transferRecordMapper.selectByTransferId(traceId);
        if (transferRecord == null) {
            throw new BadRequestException("异常的转账回调,因为transfer_record表中不存在追踪ID为" + traceId + "的记录");
        }

        //根据trace获取订单详情,然后拿到userId和tokenId,以便以更新tokenBalance表中的冻结余额
        WithdrawFlow withdrawFlow = withdrawFlowMapper.selectById(transferRecord.getWithdrawflowid());
        if (withdrawFlow == null) {
            throw new BadRequestException("异常的提现订单号[" + transferRecord.getWithdrawflowid() + "],数据库中无此记录.");
        }
        if (getStatus(callbackRequest.getStatus()).getType().equals(StatusEnum.FAIL.getType())) {
            log.info("回调了一个失败的订单,message:[{}] trace:[{}] type:[{}]", callbackRequest.getMessage(), callbackRequest.getTrace(), callbackRequest.getTxnType());
//            //说明交易失败,退回冻结的实际交易金额
//            tokenBalanceMapper.updateNormalAmount(withdrawFlow.getUserId(),withdrawFlow.getTokenId(),withdrawFlow.getRealAmount());
//            //记录流水信息,如果钱包服务回调当前订单执行失败,则记录交易金额退回流水
//            accountFlowService.insertFlow(FlowTypeEnum.withdrawRefuseBack,withdrawFlow.getUserId(),withdrawFlow.getRealAmount(),
//                    withdrawFlow.getWalletAddress(),withdrawFlow.getTokenId(),withdrawFlow.getTransferId(),callbackRequest.getTxnHash());
//
//            //交易失败,退回手续费
//            tokenBalanceMapper.updateNormalAmount(withdrawFlow.getUserId(),withdrawFlow.getTokenId(),withdrawFlow.getHandlingFee());
//            //记录流水信息,如果钱包服务回调当前订单执行失败,则记录手续费金额退回流水
//            accountFlowService.insertFlow(FlowTypeEnum.withdrawRefuseCostBack,withdrawFlow.getUserId(),withdrawFlow.getHandlingFee(),
//                    withdrawFlow.getWalletAddress(),withdrawFlow.getTokenId(),withdrawFlow.getTransferId(),callbackRequest.getTxnHash());

            //记录钱包服务回调的错误信息
            withdrawFlowMapper.updateRemake(transferRecord.getWithdrawflowid(), callbackRequest.getMessage());
        }

        //更新订单状态 (这里仅仅只是将订单提交到区块链上,等待钱包服务回调确认区块请求)
        WithdrawFlow flow = new WithdrawFlow();
        flow.setTransferId(transferRecord.getWithdrawflowid());
        flow.setStatus(getStatus(callbackRequest.getStatus()).getType());
        flow.setTxHash(callbackRequest.getTxnHash());
        withdrawFlowMapper.updateByTrace(flow);
        //更新区块链记录信息 (燃油费,交易hash,完成时间)
        transferRecordMapper.updateInfoByTransferId(buildTransferRecord(callbackRequest));
        if(getStatus(callbackRequest.getStatus()).getType().equals(StatusEnum.CONFIRM.getType())){
            //更新代币余额,从冻结金额中减去当前订单金额
            tokenBalanceMapper.updateFreezeAmount(withdrawFlow.getUserId(), withdrawFlow.getTokenId(), withdrawFlow.getTotalAmount().multiply(new BigDecimal("-1")));
        }
        //更细流水信息
        AccountFlow accountFlow = new AccountFlow();
        accountFlow.setStatus(getStatus(callbackRequest.getStatus()).getType());
        accountFlow.setTxId(callbackRequest.getTxnHash());
        accountFlow.setTransferId(withdrawFlow.getTransferId());
        accountFlowService.updateFlowInfoByTransferId(accountFlow);
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateDrawOrderInfo(CallbackRequest callbackRequest) throws Exception {
        String traceId = callbackRequest.getTrace();
        //根据trace获取订单交易记录,从中获取划转订单号
        TransferRecord transferRecord = transferRecordMapper.selectByTransferId(traceId);
        if (transferRecord == null) {
            throw new BadRequestException("异常的转账回调,因为transfer_record表中不存在追踪ID为" + traceId + "的记录");
        }

        //获取划转订单ID
        String drawFlowId = transferRecord.getWithdrawflowid();
        //获取划转订单
        DrawFlow drawFlow = drawFlowMapper.selectByDrawFlowId(drawFlowId);
        if (drawFlow == null) {
            throw new BadRequestException("异常的划转订单号[" + transferRecord.getWithdrawflowid() + "],数据库中无此记录.");
        }

        if (getStatus(callbackRequest.getStatus()).getType().equals(StatusEnum.FAIL.getType())) {
            log.info("回调了一个失败的订单,message:[{}] trace:[{}] type:[{}]", callbackRequest.getMessage(), callbackRequest.getTrace(), callbackRequest.getTxnType());
            //记录钱包服务回调的错误信息
            drawFlowMapper.updateRemake(drawFlowId, callbackRequest.getMessage());
        } else {
            //如果当前交易成功,则记录流水信息
            recordTransOfFlowInfo(drawFlow, callbackRequest);
        }

        //更新划转订单和transferRecord记录状态
        drawFlowMapper.updateStatusByTransferId(drawFlowId, getStatus(callbackRequest.getStatus()).getType());
        //更新交易hash
        drawFlowMapper.updateTxHash(drawFlowId, callbackRequest.getTxnHash());
        //更新区块链记录信息 (燃油费,交易hash,完成时间)
        transferRecordMapper.updateInfoByTransferId(buildTransferRecord(callbackRequest));
    }

    private void recordTransOfFlowInfo(DrawFlow drawFlow, CallbackRequest callbackRequest) throws Exception {

        String type = drawFlow.getType();
        Optional<TransferTypeEnum> optional = TransferTypeEnum.match(type);
        if (!optional.isPresent()) {
            log.error("异常的划转订单,因为type为空");
            return;
        }
        switch (optional.get()) {
            case IN:
                //记录划入流水
                accountFlowService.insertFlow(FlowTypeEnum.drawIn, drawFlow.getOperAccountId(), drawFlow.getDrawoutAmount(),
                        "", drawFlow.getTokenId(), drawFlow.getDrawoutId(), callbackRequest.getTxnHash());
                break;
            case OUT:
                //记录划出流水
                accountFlowService.insertFlow(FlowTypeEnum.drawOut, drawFlow.getOperAccountId(), drawFlow.getDrawoutAmount(),
                        "", drawFlow.getTokenId(), drawFlow.getDrawoutId(), callbackRequest.getTxnHash());
                break;
            default:
                log.error("异常的划转订单,因为type为空");
        }
    }

    private TransferRecord buildTransferRecord(CallbackRequest callbackRequest) {
        TransferRecord record = new TransferRecord();
        record.setGas(Long.parseLong(callbackRequest.getGas()));
        record.setStatus(getStatus(callbackRequest.getStatus()).getType());
        record.setTxhash(callbackRequest.getTxnHash());
        record.setTxnttype(callbackRequest.getTxnType());
        record.setTransferid(callbackRequest.getTrace());
        return record;
    }

    private StatusEnum getStatus(Boolean status) {
        return status ? StatusEnum.CONFIRM : StatusEnum.FAIL;
    }

    private RechargeFlow buildRechargeFlow(CallbackRequest callbackRequest) {
        RechargeFlow rechargeFlow = new RechargeFlow();
        rechargeFlow.setFromAddress(callbackRequest.getSender());
        rechargeFlow.setRealAmount(new BigDecimal(callbackRequest.getAmount()));
        rechargeFlow.setStatus(getStatus(callbackRequest.getStatus()).getType());
        rechargeFlow.setToAddress(callbackRequest.getRecepient());
        rechargeFlow.setTxId(callbackRequest.getTxnHash());
        rechargeFlow.setRechargeTime(callbackRequest.getCreateTime());
        rechargeFlow.setTokenName(callbackRequest.getTokenName());

        return rechargeFlow;
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveRechargeInfo(User user, CallbackRequest callbackRequest) {

        //根据token详细信息
        Token token = tokenMapper.selectByTokenName(callbackRequest.getTokenName());
        Assert.notNull(token, "异常的代币类型,TokenName:[" + callbackRequest.getTokenName() + "]");

        RechargeFlow rechargeFlow = buildRechargeFlow(callbackRequest);
        rechargeFlow.setUserId(user.getId());
        rechargeFlow.setPhone(user.getPhone());
        rechargeFlow.setTokenId(token.getId());
        rechargeFlow.setTransferId(UtilsService.getGuid());
        rechargeFlow.setRechargeTime(DateUtils.formatYMDHMS(new Date()));
        //保存充币记录
        rechargeFlowMapper.insert(rechargeFlow);

        TransferRecord transferRecord = buildTransferRecord(callbackRequest);
        transferRecord.setWithdrawflowid(rechargeFlow.getTransferId());
        transferRecord.setTransferamount(new BigDecimal(callbackRequest.getAmount()));
        transferRecord.setUserid(user.getId());
        transferRecord.setTokenid(token.getId());
        transferRecord.setStatus(callbackRequest.getStatus() ? StatusEnum.CONFIRM.getType() : StatusEnum.FAIL.getType());
        transferRecord.setTokenname(token.getTokenName());
        transferRecord.setCommittime(DateUtils.formatYMDHMS(new Date()));

        //生成区块链交易记录
        transferRecordMapper.insert(transferRecord);
        if (callbackRequest.getStatus()) {
            //将充值的金额暂时存入冻结余额中 (只有交易成功时,才更新余额) 等待确认区块数达到最大配置时,再解锁余额
            tokenBalanceMapper.updateFreezeAmount(user.getId(), token.getId(), new BigDecimal(callbackRequest.getAmount()));
        }
    }

    //更新transfer_record表中的区块确认数
    //根据txHash交易号查询出属于哪一类代币和属于提币或者充币
    //查询对应的配置信息,判断确认区块数是否达到要求
    //如果达到要求,更新提现/充值表的订单状态为已完成
    @Transactional(rollbackFor = Exception.class)
    public void updateTransferConfirmBlockNumber(String txnHash, Long confirmBlockNumber) throws Exception {

        //查询订单详细信息
        TransferRecord record = transferRecordMapper.selectByTxHash(txnHash);
        Assert.notNull(record, "异常的txHash,数据库中不存在此条记录");
        //更新transfer_record表中的区块确认数
        transferRecordMapper.updateBlockNumber(txnHash, confirmBlockNumber);

        //获取代币ID和订单类型(提币/充值)
        Optional<CallbackTypeEnum> optional = CallbackTypeEnum.match(record.getTxnttype());
        if (!optional.isPresent()) {
            throw new BadRequestException(ExceptionEnum.TRANSFER_TYPE_ERR);
        }

        //根据代币获取区块确认数配置
        Integer maxNumber = owcService.getSystemConfig(Const.ETH_CONFIRM_NUM /*暂时默认使用ETH*/, Integer.class);
        if (confirmBlockNumber < maxNumber) {
            //说明还未达到配置要求,返回
            return;
        }

        StatusEnum statusEnum = StatusEnum.SUCCESS;

        //更新transfer_record表中的完成时间
        transferRecordMapper.updateFinishTimeAndStatus(txnHash, DateUtils.formatYMDHMS(new Date()), statusEnum.getType());
        String flowId = record.getWithdrawflowid();
        switch (optional.get()) {
            //更新充币订单状态为成功
            case RECHARGE_TYPE:
                log.info("区块数已经满足最大配置数量,更新【充值】订单:[{}]状态为成功", flowId);
                updateRechargeFlowInfo(record, statusEnum);
                break;
            case WITHDRAW_TYPE:
                log.info("区块数已经满足最大配置数量,更新【提现】订单:[{}]状态为成功", flowId);
                updateWithdrawInfo(record, statusEnum);
                break;
            case DRAW_IN_TYPE:
            case DRAW_OUT_TYPE:
                log.info("区块数已经满足最大配置数量,更新【划转】订单:[{}]状态为成功", flowId);
                updateDrawOrderFlowInfo(record, statusEnum);
            default:
        }
    }

    private void updateWithdrawInfo(TransferRecord record, StatusEnum statusEnum) throws Exception {
        //更新流水状态
        accountFlowService.updateFlowStatus(record.getWithdrawflowid(), statusEnum);
        //更新提现记录状态
        WithdrawFlow flow = new WithdrawFlow();
        flow.setStatus(statusEnum.getType());
        flow.setFinishTime(DateUtils.formatYMDHMS(new Date()));
        flow.setTransferId(record.getWithdrawflowid());
        withdrawFlowMapper.updateByTrace(flow);

        //检查是否为平台内互转,如果是,则需要生成收款方入账记录
        WithdrawFlow withdrawFlow = withdrawFlowMapper.selectById(record.getWithdrawflowid());
        String toAddress = withdrawFlow.getWalletAddress();
        //是否属于本平台的钱包地址
        Long userId = walletService.getUserByAddress(toAddress);
        if (userId == null) {
            return;
        }
        User user = userMapper.selectByPrimaryKey(userId);
        if (user == null) {
            throw new BadRequestException("异常的用户id:[" + userId + "]");
        }
        log.info("属于平台内账户互转,生成收款人入账记录. userId:[{}] transferId:[{}]",userId,withdrawFlow.getTransferId());
        RechargeFlow rechargeFlow = new RechargeFlow();
        rechargeFlow.setUserId(userId);
        rechargeFlow.setFromAddress(withdrawFlow.getFromAddress());
        rechargeFlow.setToAddress(withdrawFlow.getWalletAddress());
        rechargeFlow.setPhone(user.getPhone());
        rechargeFlow.setRealAmount(withdrawFlow.getRealAmount());
        rechargeFlow.setRechargeTime(withdrawFlow.getCommitTime());
        rechargeFlow.setStatus(StatusEnum.SUCCESS.getType());
        rechargeFlow.setTokenId(withdrawFlow.getTokenId());
        rechargeFlow.setTokenName(withdrawFlow.getTokenName());
        rechargeFlow.setTransferId(withdrawFlow.getTransferId());
        rechargeFlow.setTxId(withdrawFlow.getTxHash());
        rechargeFlowMapper.insert(rechargeFlow);
        //入账
        tokenBalanceMapper.updateNormalAmount(userId,withdrawFlow.getTokenId(),withdrawFlow.getRealAmount());
        //生成充币流水
        accountFlowService.insertFlow(FlowTypeEnum.recharge,rechargeFlow.getUserId(),rechargeFlow.getRealAmount(),
                rechargeFlow.getToAddress(),rechargeFlow.getTokenId(),rechargeFlow.getTransferId(),rechargeFlow.getTxId());

    }

    private void updateDrawOrderFlowInfo(TransferRecord record, StatusEnum statusEnum) {

        //根据txHash获取订单信息
        DrawFlow drawFlow = drawFlowMapper.selectByHash(record.getTxhash());
        if(drawFlow == null){
            log.error("异常的txhash订单回调,因为划转表中未查询到此交易hash");
            throw new BadRequestException(ExceptionEnum.BAD_REQUEST_ERR);
        }
        //更新划转表记录为成功
        drawFlowMapper.updateStatusSuccessByDrawFlowId(drawFlow.getDrawoutId(),statusEnum.getType());
        //更新用户ETH余额 划转
//        tokenBalanceMapper.updateNormalAmount(drawFlow.getOperAccountId(),drawFlow.getTokenId(),drawFlow.getDrawoutAmount());
    }

    private void updateRechargeFlowInfo(TransferRecord record, StatusEnum statusEnum) throws Exception {
        //根据txHash获取订单信息
        RechargeFlow rechargeFlow = rechargeFlowMapper.selectByHash(record.getTxhash());
        if(rechargeFlow == null){
            log.error("异常的txhash订单回调,因为充值表中未查询到此交易hash");
            throw new BadRequestException(ExceptionEnum.BAD_REQUEST_ERR);
        }
        //解锁用户相应冻结的余额
        tokenBalanceMapper.updateBalance(rechargeFlow.getUserId(),rechargeFlow.getTokenId(),rechargeFlow.getRealAmount().multiply(new BigDecimal("-1")));
        //更新订单状态
        rechargeFlowMapper.updateStatusByUserAndTokenId(record.getWithdrawflowid(), statusEnum.getType());
        //生成充币流水
        accountFlowService.insertFlow(FlowTypeEnum.recharge,rechargeFlow.getUserId(),rechargeFlow.getRealAmount(),
                rechargeFlow.getToAddress(),rechargeFlow.getTokenId(),rechargeFlow.getTransferId(),rechargeFlow.getTxId());
        //生成平台转出流水
//        accountFlowService.insertFlow(FlowTypeEnum.platformOut,rechargeFlow.getUserId(),rechargeFlow.getRealAmount().multiply(new BigDecimal(-1)),
//                null,rechargeFlow.getTokenId(),rechargeFlow.getTransferId(),rechargeFlow.getTxId());
    }


    @Transactional(rollbackFor = Exception.class)
    public String createTransferInfoRecord(IntoToken intoToken, User user ,TransferTypeEnum transferTypeEnum) {
        //保存划转订单
        DrawFlow drawFlow = buildDrawFlowOrder(intoToken,user,transferTypeEnum.getType());
        drawFlowMapper.insert(drawFlow);

        //创建订单交易记录
        TransferRecord record = buildTransferRecord(drawFlow);
        transferRecordMapper.insert(record);
        return record.getTransferid();
    }


    /**
     * 1 根据汇率计算出兑换后的金额
     * 2 减少用户对应的兑换源代币余额
     * 3 增加用户对应的兑换目标代币余额
     * 4 生成兑换记录
     */
    @Transactional(rollbackFor = Exception.class)
    public void executeExchange(ExchangeVo exchangeVo, ExchangeType type) throws Exception {
        //token基础获取
        Token fromToken = walletService.getTokenByName(type.getMapper().getFrom());
        Token toToken = walletService.getTokenByName(type.getMapper().getTo());

        //获取对应的汇率
        BigDecimal rate = systemService.getSystemConfig().getExchangeBean().getRateMap().get(exchangeVo.getType());
        //计算
        BigDecimal targetAmount = exchangeVo.getAmount().multiply(rate);

        Long userId = SecurityContextHolder.getCurrentUserId();

        //减少用户兑换源代币余额
        tokenBalanceMapper.updateNormalAmount(userId,fromToken.getId(),exchangeVo.getAmount().multiply(new BigDecimal(-1)));
        //增加用户兑换目标代币余额
        tokenBalanceMapper.updateNormalAmount(userId,toToken.getId(),targetAmount);
        //生成兑换记录
        Exchange exchange = loadExchangeModel(fromToken,toToken,userId,rate,targetAmount,exchangeVo.getAmount(),type);
        exchangeMapper.insert(exchange);

        //兑换源代币流水
        accountFlowService.insertFlow(FlowTypeEnum.exchange,userId,exchangeVo.getAmount().multiply(new BigDecimal(-1)),"",fromToken.getId(),exchange.getOrderId() + "",null);
        //兑换目标代币流水
        accountFlowService.insertFlow(FlowTypeEnum.exchange,userId,targetAmount,"",toToken.getId(),exchange.getOrderId() + "",null);
    }
    /**
     *
     * @param fromToken    兑换源Token信息
     * @param toToken      兑换目标Token信息
     * @param userId       兑换人ID
     * @param rate         兑换汇率
     * @param targetAmount 计算后的数量
     * @param exchangeAmount 兑换原始数量
     */
    private Exchange loadExchangeModel(Token fromToken,Token toToken,Long userId,BigDecimal rate,BigDecimal targetAmount,BigDecimal exchangeAmount,ExchangeType type) {
        Exchange exchange = new Exchange();
        exchange.setAmount(exchangeAmount);
        exchange.setFromToken(fromToken.getId());
        exchange.setFromTokenName(fromToken.getTokenName());
        exchange.setRate(rate);
        exchange.setResult(targetAmount);
        exchange.setStatus(OrderType.VALID_TYPE.getType());
        exchange.setTime(DateUtils.formatYMDHMS(new Date()));
        exchange.setToToken(toToken.getId());
        exchange.setToTokenName(toToken.getTokenName());
        exchange.setUserId(userId);
        exchange.setUserPhone(SecurityContextHolder.getCurrentUserPhone());
        exchange.setType(type.getType());
        exchange.setUserName(SecurityContextHolder.getCurrentUserName());
        exchange.setOrderId(idGenerater.nextId());
        return exchange;
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveData(List<Order> expireOrders, List<IncomeRecord> incomeRecords) throws Exception {
        if(!incomeRecords.isEmpty()){
            incomeRecordMapper.batchInsert(incomeRecords);
            //生成流水信息 + 增加余额
            for (IncomeRecord incomeRecord : incomeRecords) {
                tokenBalanceMapper.updateNormalAmount(incomeRecord.getUserId(),incomeRecord.getTokenId(),incomeRecord.getIncome());
                accountFlowService.insertFlow(FlowTypeEnum.INCOME,incomeRecord.getUserId(),incomeRecord.getIncome(),"",incomeRecord.getTokenId(),"","");
            }
        }
        if(!expireOrders.isEmpty()){
            orderMapper.updateBatchSelective(expireOrders);
        }

        StringRedisTemplate stringRedisTemplate = RedisUtil.getInstance()
                .getStringRedisTemplate();

        //防止定时任务多次执行而多次发放收益,这儿记录每天已经发放的收益用户id
        stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                for (IncomeRecord order : incomeRecords) {
                    String orderManagerKey = CacheBuildKey.buildSettlementIncomeKey(DateUtils.formatYMD(returnPreviousDay(new Date())));
                    stringRedisTemplate.opsForSet().add(orderManagerKey,order.getUserId() + "");
                }
                return null;
            }
        });
    }

    private DrawFlow buildDrawFlowOrder(IntoToken intoToken, User user, String type) {
        DrawFlow drawFlow = new DrawFlow();
        drawFlow.setCommitTime(DateUtils.formatYMDHMS(new Date()));
        drawFlow.setDrawoutAmount(intoToken.getTransferAmount() == null ? new BigDecimal("-1") : intoToken.getTransferAmount());
        drawFlow.setOperAccount(user.getUsername());
        drawFlow.setOperAccountId(user.getId());
        drawFlow.setOperUser(SecurityContextHolder.getCurrentUserName());
        drawFlow.setOperId(SecurityContextHolder.getCurrentUserId());
        drawFlow.setStatus(StatusEnum.PASSED.getType());
        drawFlow.setTokenId(intoToken.getTokenId());
        drawFlow.setTokenName(intoToken.getTokenName());
        drawFlow.setType(type);
        drawFlow.setPhone(user.getPhone());
        drawFlow.setDrawoutId(UtilsService.getGuid());
        return drawFlow;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateTxHashInfo(CallbackRequest callbackRequest) {
        //获取订单信息
        String traceId = callbackRequest.getTrace();
        TransferRecord transferRecord = transferRecordMapper.selectByTransferId(traceId);

        if(transferRecord == null){
            throw new BadRequestException("异常的txHash更新回调,因为transfer_record表中不存在追踪ID为" + traceId + "的记录");
        }

        String txType = transferRecord.getTxnttype();
        Optional<CallbackTypeEnum> typeEnumOptional = CallbackTypeEnum.match(txType);
        if(!typeEnumOptional.isPresent()){
            throw new BadRequestException("异常的txHash更新回调,因为无法识别txType:[" + txType + "]");
        }
        transferRecordMapper.updateTxHashById(transferRecord.getId(),callbackRequest.getTxnHash());
        switch (typeEnumOptional.get()){
            case DRAW_IN_TYPE:
            case DRAW_OUT_TYPE:
                drawFlowMapper.updateTxHash(transferRecord.getWithdrawflowid(),callbackRequest.getTxnHash());
                break;
            case WITHDRAW_TYPE:
                withdrawFlowMapper.updateTxHash(transferRecord.getWithdrawflowid(),callbackRequest.getTxnHash());
                break;
            default:
                throw new BadRequestException("无法处理txType:["+txType+"]的txHash更新请求");
        }
        log.info("txHash更新成功. 交易单号:[{}] txHash:[{}]",transferRecord.getWithdrawflowid(),callbackRequest.getTxnHash());
    }

    public void updateCancelOrderStatus(CallbackRequest callbackRequest) {

        StatusEnum statusEnum;
        if(callbackRequest.getStatus()){
            statusEnum = StatusEnum.CANCEL;
        }else{
            statusEnum = StatusEnum.CANCELFAIL;
        }
        //获取原始hash
        String oldHash = RedisUtil._REDIS_MODULE.get(CacheBuildKey.buildReplaceTxHashKey(callbackRequest.getTxnHash()));
        TransferRecord transferRecord = transferRecordMapper.selectByTxHash(oldHash);
        log.info("取消回调订单详情:{}",JSON.toJSONString(transferRecord));
        financeService.updateOrderStatus(transferRecord,statusEnum);
        log.info("更新订单:[{}] 交易状态为:[{}]",callbackRequest.getTrace(),statusEnum.getDesc());
    }
}
